package movepaint.stateMachine;

import movepaint.*;
import java.awt.Font;
import java.awt.geom.Point2D;
import java.awt.Color;
import javax.swing.JOptionPane;
import fr.lri.swingstates.canvas.CEllipse;
import fr.lri.swingstates.canvas.CPolyLine;
import fr.lri.swingstates.canvas.CShape;
import fr.lri.swingstates.canvas.CStateMachine;
import fr.lri.swingstates.canvas.CText;
import fr.lri.swingstates.canvas.CRectangle;
import fr.lri.swingstates.canvas.Canvas;
import fr.lri.swingstates.gestures.AbstractClassifier;
import fr.lri.swingstates.gestures.Gesture;
import fr.lri.swingstates.sm.State;
import fr.lri.swingstates.sm.Transition;
import fr.lri.swingstates.sm.transitions.Drag;
import fr.lri.swingstates.sm.transitions.KeyPress;
import fr.lri.swingstates.sm.transitions.Press;
import fr.lri.swingstates.sm.transitions.Release;
import fr.lri.swingstates.sm.transitions.TimeOut;
import java.awt.BasicStroke;
import java.awt.event.KeyEvent;
import java.util.ArrayList;

/**
 * Machine a état du panel de dessin.
 * <br>
 * Gère : <br>
 * <ul>
 *  <li>Le pieMenu pour le choix des couleurs (4 par défaut + clique gauche sur une couleur pour la personnaliser)</li>
 *  <li>La reconnaissance des gestes pour dessiner</li>
 * </ul>
 * 
 * @author Paulino A.
 * @version 1.0
 */
public class InkMachine extends CStateMachine {

    public static final int SHAPE_HOVERED          = 3;
    public static final int SHAPE_NOT_HOVERED      = 1;

    // TAG pour les figures
    public static final String TAG_SHAPE = "TS";
    // TAG pour les figures surlignées
    public static final String TAG_SHAPE_HOVERED = "TSH";


    // Différence en pixel qu'il y a entre les shapes d'origines et celles copiées
    private static final int DEFAULT_DECALAGE_COPIE = 10;
    private int currentDecalageCopie;

    // List des shapes copiées
    private ArrayList<CShape> _shapesSelected = null;

    // Canvas associé au panel
    private Canvas _canvas;
    // Référence à la frame principale
    private GestureRecognition _frameMain;

    // Figures permettnt de garder en mémoire celle qui a été sélectionnée
    private CShape _shapeCopied = null;
    private CShape _shapeCliqued = null;

    // Objets utilisés pour le tracé de l'user
    private Gesture _gesture;
    private CPolyLine _ink;
    private Point2D _pInit;
    private AbstractClassifier _classifier;

    // Texte affichant le geste reconnu
    private CText recognizedClass = null;


    /**
     * Constructeur de la machine a étt du mode assisté.
     * @param classifier Classifier comprenant les gestes reconnus
     * @param canvas Canvas auquel la machine est associée
     * @param frameMain Référence à la frame principale
     */
    public InkMachine(AbstractClassifier classifier, Canvas canvas, GestureRecognition frameMain) {
        super();
        this._frameMain = frameMain;
        this._classifier = classifier;
        this._canvas = canvas;
        _gesture = new Gesture();
        createComponents();
    }

    /**
     * Création des composants
     */
    public void createComponents() {
        _shapesSelected = new ArrayList<CShape>();
        _ink = (CPolyLine) _canvas.newPolyLine().setPickable(false).setFilled(false);
        recognizedClass = _canvas.newText(GestureRecognition.WIDTH_TOOL * (_classifier.getClassesNames().size() + 2), 45, "", new Font("Verdana", Font.PLAIN, 14));
        recognizedClass.addTag(GestureRecognition.GESTE_TAG);
        _frameMain._borderColor  = Color.BLACK;
    }

    /**
     * Etat initial de la machine a état
     */
    public State start = new State() {
        boolean isSelectionMultiple = false;

        /**
         * Méthode appelée lors de la sortie de l'état
         */
        public void leave() {
            // A chaque sortie de l'état START, on cache le menu
           _frameMain.hideShapes(GestureRecognition.GESTE_TAG);
        }

        // Transition pour la suppression des shapes via la touche (SUPPR)
        Transition deleteShapes = new KeyPress(KeyEvent.VK_DELETE) {
            public void action() {
                _frameMain.deleteShapes(TAG_SHAPE_HOVERED);
            }
        };

        // Pour la fonction copier
        Transition pressOnShape = new PressOnShape(BUTTON1, ">> draw") {

            public void action() {
                if (_frameMain.drawMode() == GestureRecognition.MODE_ASSISTE) {    // MODE ASSISTE & LIBRE
                    _pInit = getPoint();
                    _ink.reset(_pInit.getX(), _pInit.getY()).setDrawable(true).aboveAll();
                    _gesture.reset();
                    _gesture.addPoint(_pInit.getX(), _pInit.getY());

                    // Ajout du shape en mode copy
                    if (getShape().hasTag(TAG_SHAPE))
                        _shapeCliqued = getShape();
                }
            }
        };

        // Tracé du geste de l'user
        Transition begin = new Press(BUTTON1, ">> draw") {

            public void action() {
                // Point de départ
                _pInit = getPoint();

                if (_frameMain.drawMode() == GestureRecognition.MODE_ASSISTE) {    // MODE ASSISTE & LIBRE
                    _ink.reset(_pInit.getX(), _pInit.getY()).setDrawable(true).aboveAll();
                    _gesture.reset();
                    _gesture.addPoint(_pInit.getX(), _pInit.getY());
                }
            }
        };
    };

    /**
     * Statut permettant le dessin ou le déplacement des shapes
     */
    public State draw = new State() {

        /**
         * Commence a dessiner la ligne tracée par l'user
         */
        Transition drag = new Drag(BUTTON1) {
            public void action() {
                if (_frameMain.drawMode() == GestureRecognition.MODE_ASSISTE) {
                    _ink.lineTo(getPoint().getX(), getPoint().getY()).setOutlinePaint(_frameMain._borderColor);
                    _gesture.addPoint(getPoint().getX(), getPoint().getY());
                }
            }
        };

        // On garde ne memoire le shape a effacer
        Transition dragOnShape = new DragOnShape(BUTTON1) {
            public void action() {
                // En mode deplacement, on change les coordonnées de la/les shapes sélectionnées
                if (_frameMain.drawMode() == GestureRecognition.MODE_ASSISTE) {
                    _ink.lineTo(getPoint().getX(), getPoint().getY()).setOutlinePaint(_frameMain._borderColor);
                    _gesture.addPoint(getPoint().getX(), getPoint().getY());

                    // On verifie que le tag est bien un rectangle
                    if (getShape().hasTag(TAG_SHAPE)) {
                        getShape().setStroke(new BasicStroke(SHAPE_HOVERED));
                        getShape().addTag(TAG_SHAPE_HOVERED);
                    }
                }
            }
        };
        Transition end = new Release(BUTTON1, ">> feedback") {

            public void action() {
                armTimer(100, true);
                _gesture.addPoint(getPoint().getX(), getPoint().getY());
                String gc = _classifier.classify(_gesture);
                CRectangle cadre = _ink.getBoundingBox();

                // Si l'user est en mode "Dessin libre", on trace seulement la ligne de l'user
                if (_frameMain.drawMode() == GestureRecognition.MODE_ASSISTE) {
                    // On réinitialise les bordures
                    for (CShape shape : _canvas.getDisplayList()) {
                        shape.setStroke(new BasicStroke(SHAPE_NOT_HOVERED));
                    }
                    
                    if (gc != null) {
                        recognizedClass.setText(gc);

                        if (gc.equals("Effacer")) {
                            _frameMain.deleteShapes(TAG_SHAPE_HOVERED);
                        } else if (gc.equals("Coller")) {
                            pastShape();
                        } else if (gc.equals("Copier")) {
                            copyShape();
                        } else if (gc.equals("Cercle")) {
                            drawCircle(cadre);
                        } else if (gc.equals("Rectangle")) {
                            drawRectangle(cadre);
                        } else if (gc.equals("Ecrire")) {
                            writeText();
                        }

                        // Delete tag of the others tag
                        _canvas.removeTag(TAG_SHAPE_HOVERED);
                    } else {
                        recognizedClass.setText("?");
                    }
                }
            }

            private void copyShape() {
                // On enleve le surlignage des anciennes shapes
                for (CShape shape: _shapesSelected)
                    shape.setStroke(new BasicStroke(SHAPE_NOT_HOVERED));

                // On vide les shapes précédentes
                _shapesSelected.clear();

                // On copie seulement celle selectionnée
                if (_shapeCliqued != null) {
                    _shapeCliqued.setStroke(new BasicStroke(SHAPE_HOVERED));
                    _shapesSelected.add(_shapeCliqued);
                }
            }

            private void drawRectangle(CRectangle cadre) {
                _canvas.newRectangle(cadre.getMinX(), cadre.getMinY(), cadre.getMaxX() - cadre.getMinX(), cadre.getMaxY() - cadre.getMinY()).setFillPaint(_frameMain._backgroundColor).addTag(TAG_SHAPE).setOutlinePaint(_frameMain._borderColor);
            }

            private void writeText() {
                // On recupere le texte
                String res = JOptionPane.showInputDialog("Please enter your text:");

                // On affiche le texte ecrit
                if (res != null) {
                    _canvas.newText(_pInit.getX(), _pInit.getY(), res).setFillPaint(_frameMain._backgroundColor).addTag(TAG_SHAPE).setOutlinePaint(_frameMain._borderColor);
                }
            }

            private void drawCircle(CRectangle cadre) {
                _canvas.newEllipse(cadre.getMinX(), cadre.getMinY(), cadre.getMaxX() - cadre.getMinX(), cadre.getMaxY() - cadre.getMinY()).setFillPaint(_frameMain._backgroundColor).addTag(TAG_SHAPE).setOutlined(true).setOutlinePaint(_frameMain._borderColor);
            }

            private void pastShape() {
                // On vérifie qu'il existe des shapes copiées
                if (!_shapesSelected.isEmpty()) {
                    // On copie chacune des shapes
                    for (CShape shape: _shapesSelected) {
                        _shapeCopied = shape.duplicateWithTags();
                        _shapeCopied.translateTo(_pInit.getX(), _pInit.getY()).setDrawable(true);

                        // On surligne les shapes d'origines
                        shape.setStroke(new BasicStroke(SHAPE_HOVERED));
                    }
                }
            }
        };
    };
    public State feedback = new State() {

        Transition timeout = new TimeOut(">> start") {

            public void action() {
                _frameMain._statusBar.isWork(false);
                _ink.setDrawable(false);
                _frameMain.showShapes(GestureRecognition.GESTE_TAG);
            }
        };
    };

    /**
     * Sélectionne tous les shapes (correspond à tous les marquer avec le TAG
     *  TAG_SHAPE_HOVERED
     */
    public void selectAllShapes() {
        if (_canvas.getTag(TAG_SHAPE) != null) {
            _canvas.getTag(TAG_SHAPE).setStroke(new BasicStroke(SHAPE_HOVERED));
            _canvas.getTag(TAG_SHAPE).addTag(TAG_SHAPE_HOVERED);
        }
    }

    public void copySelectedShapes() {
        // On vide les shapes précédentes
        _shapesSelected.clear();

        // On réinitialise le décalage des copies
        currentDecalageCopie = DEFAULT_DECALAGE_COPIE;

        // On copie toutes les shapes sélectionnées
        for(CShape shape : _canvas.getDisplayList()) {
            if (shape.hasTag(TAG_SHAPE_HOVERED)) {
                _shapesSelected.add(shape);
            }
        }
    }

    public void pasteSelectedShapes() {
        // On vérifie qu'il existe des shapes copiées
        if (!_shapesSelected.isEmpty()) {
            // On copie chacune des shapes
            for (CShape shape: _shapesSelected) {
                _shapeCopied = shape.duplicate();
                _shapeCopied.addTag(TAG_SHAPE);
                _shapeCopied.setStroke(new BasicStroke(SHAPE_NOT_HOVERED));
                _shapeCopied.translateTo(shape.getCenterX() + currentDecalageCopie,
                                        shape.getCenterY() + currentDecalageCopie).setDrawable(true);

                // On surligne les shapes d'origines
                shape.setStroke(new BasicStroke(SHAPE_HOVERED));
            }

            // On incrément le décalage des copies (dans le cas de copie successive)
            currentDecalageCopie += DEFAULT_DECALAGE_COPIE;
        }
    }
}